﻿using System;
using System.Collections.Generic;
using System.Text;
using DO.Clustering;

namespace BLL.Relations.VectorDistances
{
    /// <summary>
    /// 
    /// </summary>
    public class CosineDistanceCalculator:IDistanceCalculator
    {
       
        #region IDistanceCalculator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            ref Dictionary<int, int> contributingFeatures)
        {
            if (FeatureFreq1 == null || FeatureFreq2 == null || FeatureFreq1.Count == 0 || FeatureFreq2.Count == 0)
                return double.PositiveInfinity;

            Dictionary<int, int> unionFeatureIDs1 = new Dictionary<int, int>();
            Dictionary<int, int> unionFeatureIDs2 = new Dictionary<int, int>();
            foreach(int FeatureID1 in FeatureFreq1.Keys)
            {
                unionFeatureIDs1.Add(FeatureID1,1);
                if(FeatureFreq2.ContainsKey(FeatureID1))
                {
                    unionFeatureIDs2.Add(FeatureID1, FeatureFreq1[FeatureID1].Count);
                }
                else
                {
                    unionFeatureIDs2.Add(FeatureID1,0);
                }
            }
            foreach(int FeatureID2 in FeatureFreq2.Keys)
            {
                if(!unionFeatureIDs2.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs2.Add(FeatureID2,FeatureFreq2[FeatureID2].Count);
                }
                if(!unionFeatureIDs1.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs1.Add(FeatureID2,0);
                }
            }

            double length1 = 0;
            double length2 = 0;

            //sum of vector multiplication
            double svMul = 0;

            foreach (int FeatureID in unionFeatureIDs1.Keys)
            {
                double freq1 = unionFeatureIDs1[FeatureID];
                length1 += Math.Pow(freq1, 2);
                double freq2 = unionFeatureIDs2[FeatureID];
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if(freq1>0 && freq2>0)
                {
                    contributingFeatures.Add(FeatureID, Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count));
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoCommonFeatureScale(
                    FeatureFreq1.Count, FeatureFreq2.Count,
                    contributingFeatures.Count);
            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            cosineDistance = cosineDistance*docLenScale;
            return cosineDistance*contributingFeatureScale;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="FeatureFreq1"></param>
        /// <param name="FeatureFreq2"></param>
        /// <param name="FeatureWeights"></param>
        /// <param name="useNGramSimilarity"></param>
        /// <param name="contributingFeatures"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, FeatureFreq> FeatureFreq1,
            Dictionary<int, FeatureFreq> FeatureFreq2,
            Dictionary<int, double> FeatureWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingFeatures)
        {
            Dictionary<int, double> unionFeatureIDs1 = new Dictionary<int, double>();
            Dictionary<int, double> unionFeatureIDs2 = new Dictionary<int, double>();
            foreach (int FeatureID1 in FeatureFreq1.Keys)
            {
                unionFeatureIDs1.Add(FeatureID1, FeatureFreq1[FeatureID1].Weight * FeatureFreq1[FeatureID1].Count);
                if (FeatureFreq2.ContainsKey(FeatureID1))
                {
                    unionFeatureIDs2.Add(FeatureID1, FeatureFreq2[FeatureID1].Count * FeatureFreq2[FeatureID1].Weight);
                }
                else
                {
                    unionFeatureIDs2.Add(FeatureID1, 0);
                }
            }
            foreach (int FeatureID2 in FeatureFreq2.Keys)
            {
                if (!unionFeatureIDs2.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs2.Add(FeatureID2, FeatureFreq2[FeatureID2].Count * FeatureFreq2[FeatureID2].Weight);
                }
                if (!unionFeatureIDs1.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs1.Add(FeatureID2, 0);
                }
            }

            double length1 = 0;
            double length2 = 0;
            Dictionary<int, Dictionary<int, double>> featureSimilarityMatrix =
                FeatureSimilarityMatrix.BuildFeatureDistanceMatrixUsingNgram(
                    FeatureFreq1, FeatureFreq2);

            //sum of vector multiplication
            double svMul = 0;

            foreach (int FeatureID in unionFeatureIDs1.Keys)
            {
                double freq1 = unionFeatureIDs1[FeatureID];
                double freq2 = unionFeatureIDs2[FeatureID];
                if (useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarFeatures = featureSimilarityMatrix[FeatureID];
                    foreach (int similarFeatureID in similarFeatures.Keys)
                    {
                        if (unionFeatureIDs1[similarFeatureID] > freq1)
                            freq1 = unionFeatureIDs1[similarFeatureID];
                        if (unionFeatureIDs2[similarFeatureID] > freq2)
                            freq2 = unionFeatureIDs2[similarFeatureID];
                    }
                }

                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                        count = Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count);
                    else if(FeatureFreq1.ContainsKey(FeatureID))
                        count = FeatureFreq1[FeatureID].Count;
                    else if(FeatureFreq2.ContainsKey(FeatureID))
                        count = FeatureFreq2[FeatureID].Count;
                    contributingFeatures.Add(FeatureID, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);
            
            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            cosineDistance = cosineDistance*docLenScale;

            //Dictionary<int,double> contributingFeatureWeights=new Dictionary<int, double>();
            //foreach (int sharedFeatureID in contributingFeatures.Keys)
            //{
            //    if (FeatureWeights.ContainsKey(sharedFeatureID))
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, FeatureWeights[sharedFeatureID]);
            //    }
            //    else
            //    {
            //        contributingFeatureWeights.Add(sharedFeatureID, 0);
            //    }
            //}
            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);
            
            return cosineDistance * contributingFeatureScale;
        }

        public double CalculateDistance(Dictionary<int, FeatureFreq> FeatureFreq1, Dictionary<int, FeatureFreq> FeatureFreq2,
                                        Dictionary<int, double> FeatureWeights, Dictionary<int, string> FeatureStemmings1,
                                        Dictionary<string, List<int>> FeatureStemmings2,
                                        ref Dictionary<int, int> contributingFeatures)
        {
            Dictionary<int, double> unionFeatureIDs1 = new Dictionary<int, double>();
            Dictionary<int, double> unionFeatureIDs2 = new Dictionary<int, double>();
            foreach (int FeatureID1 in FeatureFreq1.Keys)
            {
                unionFeatureIDs1.Add(FeatureID1, FeatureFreq1[FeatureID1].Weight * FeatureFreq1[FeatureID1].Count);
                if (FeatureFreq2.ContainsKey(FeatureID1))
                {
                    unionFeatureIDs2.Add(FeatureID1, FeatureFreq2[FeatureID1].Count * FeatureFreq2[FeatureID1].Weight);
                }
                else
                {
                    unionFeatureIDs2.Add(FeatureID1, 0);
                }
            }
            foreach (int FeatureID2 in FeatureFreq2.Keys)
            {
                if (!unionFeatureIDs2.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs2.Add(FeatureID2, FeatureFreq2[FeatureID2].Count * FeatureFreq2[FeatureID2].Weight);
                }
                if (!unionFeatureIDs1.ContainsKey(FeatureID2))
                {
                    unionFeatureIDs1.Add(FeatureID2, 0);
                }
            }

            double length1 = 0;
            double length2 = 0;

            //sum of vector multiplication
            double svMul = 0;

            foreach (int FeatureID in unionFeatureIDs1.Keys)
            {
                double freq1 = unionFeatureIDs1[FeatureID];
                double freq2 = unionFeatureIDs2[FeatureID];

                List<int> sharedFeatureIDs = FeatureStemmings2[FeatureStemmings1[FeatureID]];
                List<int> sharedFeatureIDs1 = new List<int>();
                List<int> sharedFeatureIDs2 = new List<int>();
                foreach (int FeatureID1 in FeatureFreq1.Keys)
                {
                    if (sharedFeatureIDs.Contains(FeatureID1))
                    {
                        sharedFeatureIDs1.Add(FeatureID1);
                    }
                }
                foreach (int FeatureID2 in FeatureFreq2.Keys)
                {
                    if (sharedFeatureIDs.Contains(FeatureID2))
                    {
                        sharedFeatureIDs2.Add(FeatureID2);
                    }
                }
                if (sharedFeatureIDs1.Count>0 && sharedFeatureIDs2.Count>0)
                {
                    foreach (int similarFeatureID in sharedFeatureIDs1)
                    {
                        if (unionFeatureIDs1[similarFeatureID] > freq1)
                            freq1 = unionFeatureIDs1[similarFeatureID];
                    }
                    foreach (int similarFeatureID in sharedFeatureIDs2)
                    {
                        if (unionFeatureIDs2[similarFeatureID] > freq2)
                            freq2 = unionFeatureIDs2[similarFeatureID];
                    }
                }

                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (FeatureFreq1.ContainsKey(FeatureID) && FeatureFreq2.ContainsKey(FeatureID))
                        count = Math.Min(FeatureFreq1[FeatureID].Count, FeatureFreq2[FeatureID].Count);
                    else if (FeatureFreq1.ContainsKey(FeatureID))
                        count = FeatureFreq1[FeatureID].Count;
                    else if (FeatureFreq2.ContainsKey(FeatureID))
                        count = FeatureFreq2[FeatureID].Count;
                    contributingFeatures.Add(FeatureID, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingFeatures.Count * 2 /
                                 (FeatureFreq1.Count + FeatureFreq2.Count - contributingFeatures.Count);
            cosineDistance = cosineDistance * docLenScale;

            double contributingFeatureScale =
                FeatureWeightCalculator.TaminoWeightedFeatureScale(
                    FeatureFreq1, FeatureFreq2,
                    FeatureWeights);

            return cosineDistance * contributingFeatureScale;
        }

        #endregion
    }
}
